package com.archive.core.statistics;

import com.archive.core.table.ArchiveTable;
import com.archive.core.util.ArchiveUtil;
import com.archive.core.util.SystemClock;
import lombok.Data;

import java.util.*;
import java.util.function.Supplier;

/**
 * 性能统计信息
 */
@Data
public class ArchiveStatistics {

    private Map<String, Long> archiveTableStartTime;
    private Map<String, Long> archiveTableStartCostTime;
    private Map<String, Long> archiveTableEndTime;
    private Map<String, Long> archiveTableEndCostTime;
    private Map<String, Long> archiveTableBatchSize;
    private Map<String, Long> archiveTableRecordCount;
    private Map<String, Long> archiveTableCostTime;
    private Map<String, Long> archiveTableQueryStartTime;
    private Map<String, Long> archiveTableQueryEndTime;
    private Map<String, Long> archiveTableExecuteInsertStartTime;
    private Map<String, Long> archiveTableExecuteInsertEndTime;
    private Map<String, Long> archiveTableExecuteDeleteStartTime;
    private Map<String, Long> archiveTableExecuteDeleteEndTime;
    private Integer maxTableLength;
    private Long startTime;
    private Long endTime;

    public ArchiveStatistics() {
        this.archiveTableStartTime = new LinkedHashMap<>();
        this.archiveTableStartCostTime = new HashMap<>();
        this.archiveTableEndTime = new HashMap<>();
        this.archiveTableEndCostTime = new HashMap<>();
        this.archiveTableRecordCount = new HashMap<>();
        this.archiveTableBatchSize = new HashMap<>();
        this.archiveTableCostTime = new HashMap<>();
        this.archiveTableQueryStartTime = new HashMap<>();
        this.archiveTableQueryEndTime = new HashMap<>();
        this.archiveTableExecuteInsertStartTime = new HashMap<>();
        this.archiveTableExecuteInsertEndTime = new HashMap<>();
        this.archiveTableExecuteDeleteStartTime = new HashMap<>();
        this.archiveTableExecuteDeleteEndTime = new HashMap<>();
        this.maxTableLength = 5;
    }

    public void start() {
        this.startTime = SystemClock.now();
    }

    public void end() {
        this.endTime = SystemClock.now();
    }

    public void startTable(ArchiveTable archiveTable) {
        String levelTableName = archiveTable.getLevelTableName();
        String tableName = archiveTable.getTableName();
        archiveTableStartTime.computeIfAbsent(levelTableName, tName -> {
            long currentTimeMillis = SystemClock.now();
            archiveTableStartCostTime.put(levelTableName, 0L);
            archiveTableEndTime.put(levelTableName, currentTimeMillis);
            archiveTableEndCostTime.put(levelTableName, 0L);
            archiveTableBatchSize.put(levelTableName, archiveTable.getBatchSize().longValue());
            archiveTableRecordCount.put(levelTableName, 0L);
            archiveTableCostTime.put(levelTableName, 0L);
            archiveTableQueryStartTime.put(levelTableName, 0L);
            archiveTableQueryEndTime.put(levelTableName, 0L);
            archiveTableExecuteInsertStartTime.put(levelTableName, 0L);
            archiveTableExecuteInsertEndTime.put(levelTableName, 0L);
            archiveTableExecuteDeleteStartTime.put(levelTableName, 0L);
            archiveTableExecuteDeleteEndTime.put(levelTableName, 0L);
            if (tableName.length() > maxTableLength) {
                maxTableLength = tableName.length();
            }
            return currentTimeMillis;
        });
        accumulateTime(archiveTableStartCostTime, levelTableName);
    }

    public void endTable(ArchiveTable archiveTable) {
        archiveTableEndTime.put(archiveTable.getLevelTableName(), SystemClock.now());
        accumulateTime(archiveTableEndCostTime, archiveTable.getLevelTableName());
    }

    public void increaseRecordCount(ArchiveTable archiveTable) {
        archiveTableRecordCount.put(archiveTable.getLevelTableName(), archiveTableRecordCount.get(archiveTable.getLevelTableName()) + archiveTable.getData().size());
    }

    public void queryStart(ArchiveTable archiveTable) {
        accumulateTime(archiveTableQueryStartTime, archiveTable.getLevelTableName());
    }

    public void queryEnd(ArchiveTable archiveTable) {
        accumulateTime(archiveTableQueryEndTime, archiveTable.getLevelTableName());
    }

    public void executeInsertStart(ArchiveTable archiveTable) {
        accumulateTime(archiveTableExecuteInsertStartTime, archiveTable.getLevelTableName());
    }

    public void executeInsertEnd(ArchiveTable archiveTable) {
        accumulateTime(archiveTableExecuteInsertEndTime, archiveTable.getLevelTableName());
    }

    public void executeDeleteStart(ArchiveTable archiveTable) {
        accumulateTime(archiveTableExecuteDeleteStartTime, archiveTable.getLevelTableName());
    }

    public void executeDeleteEnd(ArchiveTable archiveTable) {
        accumulateTime(archiveTableExecuteDeleteEndTime, archiveTable.getLevelTableName());
    }

    private void accumulateTime(Map<String, Long> timeMap, String tableName) {
        timeMap.put(tableName, timeMap.get(tableName) + (SystemClock.now() - archiveTableStartTime.get(tableName)));
    }


    /**
     * <pre>
     * +-------+------------+------------+----------+--------------+-----------+----------------+------------+-------------+-------------+
     * | table | batch_size | start_time | end_time | record_count | cost_time | real_cost_time | query_time | insert_time | delete_time |
     * +-------+------------+------------+----------+--------------+-----------+----------------+------------+-------------+-------------+
     * |       |            |            |          |              |           |                |            |             |             |
     * +-------+------------+------------+----------+--------------+-----------+----------------+------------+-------------+-------------+
     * </pre>
     */
    public String print(Map<String, ArchiveTable> archiveTableMap, long archiveTimeStamp) {
        StringBuilder statisticsInfo = new StringBuilder("archive timestamp: ").append(archiveTimeStamp)
                .append(", startTime: ").append(ArchiveUtil.formatTime(startTime))
                .append(", end time: ").append(ArchiveUtil.formatTime(endTime))
                .append(", total cost: ").append(endTime - startTime).append("ms, table cost info:");

        if (archiveTableStartTime.isEmpty()) {
            return statisticsInfo.append(" no archive table has been processed").toString();
        }

        int[] columnLengths = new int[]{maxTableLength, 10, 13, 13, 12, 9, 14, 10, 11, 11};

        StringBuilder splitLine = getSplitLine(columnLengths);
        statisticsInfo.append(splitLine);
        StringBuilder header = getTableHeader();
        statisticsInfo.append(header);
        statisticsInfo.append(splitLine);

        archiveTableCostTime.keySet().forEach(tableName -> {
            Long startCostTime = archiveTableStartCostTime.get(tableName);
            Long endCostTime = archiveTableEndCostTime.get(tableName);
            archiveTableCostTime.put(tableName, endCostTime - startCostTime);
        });

        archiveTableStartTime.forEach((tableName, tableStartTime) ->
                appendArchiveTable(statisticsInfo, splitLine, archiveTableMap.get(tableName), columnLengths));

        return statisticsInfo.toString();
    }


    private void appendArchiveTable(StringBuilder statisticsInfo, StringBuilder splitLine, ArchiveTable archiveTable, int[] columnLengths) {
        String levelTableName = archiveTable.getLevelTableName();
        List<Supplier<String>> suppliers = new ArrayList<>();
        // tableName
        suppliers.add(archiveTable::getTableName);
        // batch_size
        suppliers.add(() -> String.valueOf(archiveTableBatchSize.get(levelTableName)));
        // start_time
        suppliers.add(() -> String.valueOf(archiveTableStartTime.get(levelTableName)));
        // end_time
        suppliers.add(() -> String.valueOf(archiveTableEndTime.get(levelTableName)));
        // record_count
        suppliers.add(() -> String.valueOf(archiveTableRecordCount.get(levelTableName)));
        // cost_time
        suppliers.add(() -> String.valueOf(archiveTableCostTime.get(levelTableName)));
        // real_cost_time
        suppliers.add(() -> {
            Long costTime = archiveTableCostTime.get(levelTableName);
            List<ArchiveTable> relatedTables = archiveTable.getRelatedTables();
            if (relatedTables != null && !relatedTables.isEmpty()) {
                for (ArchiveTable relatedTable : relatedTables) {
                    String relatedTableName = relatedTable.getLevelTableName();
                    if (archiveTableCostTime.containsKey(relatedTableName)) {
                        costTime -= archiveTableCostTime.get(relatedTableName);
                    }
                }
            }
            return String.valueOf(costTime);
        });
        // query_time
        suppliers.add(() -> String.valueOf(archiveTableQueryEndTime.get(levelTableName) - archiveTableQueryStartTime.get(levelTableName)));
        // insert_time
        suppliers.add(() -> String.valueOf(archiveTableExecuteInsertEndTime.get(levelTableName) - archiveTableExecuteInsertStartTime.get(levelTableName)));
        // delete_time
        suppliers.add(() -> String.valueOf(archiveTableExecuteDeleteEndTime.get(levelTableName) - archiveTableExecuteDeleteStartTime.get(levelTableName)));

        for (int i = 0; i < columnLengths.length; i++) {
            appendColumn(statisticsInfo, suppliers.get(i).get(), columnLengths[i]);
        }

        statisticsInfo.append("|");
        statisticsInfo.append(splitLine);
    }

    private void appendColumn(StringBuilder statisticsInfo, String columnName, int columnLength) {
        statisticsInfo.append("| ").append(columnName);
        for (int i = 0; i < columnLength - columnName.length(); i++) {
            statisticsInfo.append(" ");
        }
        statisticsInfo.append(" ");
    }

    private StringBuilder getTableHeader() {
        StringBuilder statisticsInfo = new StringBuilder();
        statisticsInfo.append("| table");
        if (maxTableLength > 5) {
            for (int j = 5; j < maxTableLength; j++) {
                statisticsInfo.append(" ");
            }
        }
        statisticsInfo.append(" |");
        statisticsInfo.append(" batch_size |");
        statisticsInfo.append(" start_time    |");
        statisticsInfo.append(" end_time      |");
        statisticsInfo.append(" record_count |");
        statisticsInfo.append(" cost_time |");
        statisticsInfo.append(" real_cost_time |");
        statisticsInfo.append(" query_time |");
        statisticsInfo.append(" insert_time |");
        statisticsInfo.append(" delete_time |");

        return statisticsInfo;
    }

    private StringBuilder getSplitLine(int[] columnLengths) {
        StringBuilder splitLine = new StringBuilder("\n+");
        for (int columnLength : columnLengths) {
            for (int j = 0; j < columnLength + 2; j++) {
                splitLine.append("-");
            }
            splitLine.append("+");
        }
        splitLine.append("\n");
        return splitLine;
    }

}